const wxCrypto = require('./crypto.js'),
    xml2js = require('xml2js');


const UTILS = {
    /*!
     * 将xml2js解析出来的对象转换成直接可访问的对象
     */
    formatMessage(result) {
        let message = {};
        if (typeof result === 'object') {
            for (let key in result) {
                if (!(result[key] instanceof Array) || result[key].length === 0) {
                    continue;
                }
                if (result[key].length === 1) {
                    let val = result[key][0];
                    if (typeof val === 'object') {
                        message[key] = UTILS.formatMessage(val);
                    }
                    else {
                        message[key] = (val || '').trim();
                    }
                }
                else {
                    message[key] = [];
                    result[key].forEach(function(item) {
                        message[key].push(UTILS.formatMessage(item));
                    });
                }
            }
            return message;
        }
        else {
            return result;
        }
    },
    parse(str) {
        return new Promise((resolve, reject) => {
            xml2js.parseString(str, {
                trim: true
            }, function(err, result) {
                if (err) {
                    resolve({
                        success: false,
                        msg: err.name
                    })
                    return;
                }
                resolve({
                    success: true,
                    body: UTILS.formatMessage(result.xml)
                });
            });
        })
    },
    loadRawBody(req) {
        // support content-type 'text/xml' using 'express-xml-bodyparser', which set raw xml string
        // to 'req.rawBody'(while latest body-parser no longer set req.rawBody), see
        // https://github.com/macedigital/express-xml-bodyparser/blob/master/lib/types/xml.js#L79
        if (req.rawBody) {
            return Promise.resolve(req.rawBody);
        }

        let buffers = [];
        req.on('data', function(trunk) {
            buffers.push(trunk);
        });
        return new Promise((res, rej) => {
            req.on('end', function() {
                res(Buffer.concat(buffers));
            });
            req.once('error', err => rej(err));
        })
    }
}
const REPLY_UTILS = {
    injectReply(response) {
        response.replys = {};
        response.reply = function(messages) {
            if (typeof messages == 'string') {
                response.replys = {
                    type: 'text',
                    content: messages
                }
            }
            else {
                response.replys = messages;
            }
        };
    },
    toXml(instance) {
        let {
            request,
            response
        } = instance;
        let {
            ToUserName,
            FromUserName
        } = request.weixin;
        let replys = response.replys;
        let {
            type,
            content
        } = replys, xml = '';
        switch (type) {
            default: xml = `<xml>
        	<Content><![CDATA[${content}]]></Content>
        	</xml>`;
        }
        xml = `<MsgType><![CDATA[${type}]]></MsgType><Content><![CDATA[${content}]]></Content>`;

        xml = `<xml>
   	<ToUserName><![CDATA[${FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${ToUserName}]]></FromUserName>
    <CreateTime>${+new Date}</CreateTime>${xml}
   	</xml>`.replace(/>(\s+)?\r?\n(\s+)?</gi, '><').replace(/^\s|\s$/gi, '');
        if (!request.query.encrypt_type || request.query.encrypt_type === 'raw') {
            return xml;
        }
        let encrypt = crypto.encrypt(xml),
            timestamp = +new Date,
            nonce = ~~(Math.random() * 100000000000),
            signature = crypto.getSignature(timestamp, nonce, encrypt);

        return `<xml>
  <Encrypt><![CDATA[${encrypt}]]></Encrypt>
  <MsgSignature><![CDATA[${signature}]]></MsgSignature>
  <TimeStamp>${timestamp}</TimeStamp>
  <Nonce><![CDATA[${nonce}]]></Nonce>
</xml>`.replace(/>(\s+)?\r?\n(\s+)?</gi, '><').replace(/^\s|\s$/gi, '');
    }
}

const injectReply = function(response) {
    response.replys = {};
    response.reply = function(messages) {
        if (typeof messages == 'string') {
            response.replys = {
                type: 'text',
                content: messages
            }
        }
        else {
            response.replys = messages;
        }
    };
}

let crypto = null;

module.exports = options => {
    return function* parseWx(next) {
        if (crypto == null) {
            let {
                token,
                encodingAESKey,
                appid
            } = this.app.config.weixin;
            crypto = new wxCrypto({
                token,
                encodingAESKey,
                id: appid
            });
        }
        let method = this.request.method;
        let {
            signature,
            timestamp,
            nonce,
            echostr
        } = this.request.query;

        // 全局验证 [[start
        if (!crypto.checkSignature(timestamp, nonce, this.request.wechat_token, signature)) {
            this.response.status = 401;
            this.response.body = 'Invalid signature';
            return;
        }
        // 全局验证 [[end

        // 配置验证  [[start
        // 开发者通过检验signature对请求进行校验（下面有校验方式）。
        // 若确认此次GET请求来自微信服务器，请原样返回echostr参数内容，则接入生效，否则接入失败。
        if (method == 'GET') {
            this.response.status = 200;
            this.response.body = echostr;
            return;
        }
        // 配置验证  [[end

        // 获取body [[start
        let rawBody = null;
        try {
            rawBody = yield UTILS.loadRawBody(this.req);
        }
        catch (e) {
            this.response.status = 500;
            this.response.body = 'Message recieve error: ' + err.name;
            console.info('Message recieve error: ' + err.name);
            return;
        }

        if (!rawBody) {
            this.response.status = 500;
            this.response.body = 'Empty message';
            console.info(`Empty message`)
            return;
        }
        // 获取body [[end
        let result = yield UTILS.parse(rawBody);
        if (!result.success) {
            this.response.status = 500;
            this.response.body = 'BadMessage ' + result.msg;
            console.info('BadMessage ' + result.msg)
            return;
        }

        let {
            ToUserName,
            Encrypt
        } = result.body, {
            msg_signature
        } = this.request.query;

        if (msg_signature !== crypto.getSignature(timestamp, nonce, Encrypt)) {
            this.response.status = 401;
            this.response.body = 'Invalid message signature';
            return;
        }

        var decrypted = crypto.decrypt(Encrypt);
        var messageWrapXml = decrypted.message;
        if (messageWrapXml === '') {
            this.response.status = 401;
            this.response.body = 'Invalid appid';
            return;
        }
        let {
            success,
            msg,
            body
        } = yield UTILS.parse(messageWrapXml);
        if (!success) {
            this.response.status = 500;
            this.response.body = 'BadMessage ' + msg;
            console.info('BadMessage ' + msg)
            return;
        }

        body.msg_signature = msg_signature;
        body.ToUserName = ToUserName;
        // Object.def
        this.request.weixin = body;
        REPLY_UTILS.injectReply(this.response)
        yield next;

        let _body = REPLY_UTILS.toXml(this);
        this.response.status = 200;
        this.response.body = _body;
    }
}